package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.*;
import com.atguigu.gmall.product.client.ProductFeignClient;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    //商品上架--将数据封装到Goods 并且将Goods保存到es中
    @Override
    public void upperGoods(Long skuId) {
        Goods goods = new Goods();
        //给goods添加数据
        //远程调用获取数据
        SkuInfo skuInfo = this.productFeignClient.getSkuInfo(skuId);
        goods.setId(skuId);
        //商品名字
        goods.setTitle(skuInfo.getSkuName());
        goods.setDefaultImg(skuInfo.getSkuDefaultImg());
        //skuInfo 可能是从缓存中获取的数据,会出现数据不一致---延迟双删
        //goods.setPrice(skuInfo.getPrice().doubleValue());
        //为了避免脏读，查询数据库
        goods.setPrice(this.productFeignClient.getSkuPrice(skuId).doubleValue());
        //当前商品上架时间
        goods.setCreateTime(new Date());
        //赋值品牌数据
        BaseTrademark trademark = this.productFeignClient.getTrademark(skuInfo.getTmId());
        goods.setTmId(trademark.getId());
        goods.setTmName(trademark.getTmName());
        goods.setTmLogoUrl(trademark.getLogoUrl());

        //赋值分类数据
        BaseCategoryView categoryView = this.productFeignClient.getCategoryView(skuInfo.getCategory3Id());
        goods.setCategory1Id(categoryView.getCategory1Id());
        goods.setCategory2Id(categoryView.getCategory2Id());
        goods.setCategory3Id(categoryView.getCategory3Id());

        goods.setCategory1Name(categoryView.getCategory1Name());
        goods.setCategory2Name(categoryView.getCategory2Name());
        goods.setCategory3Name(categoryView.getCategory3Name());

        //热度排名：默认值0
        //赋值平台属性
        List<BaseAttrInfo> attrList = this.productFeignClient.getAttrList(skuId);
        //循环遍历
        List<SearchAttr> searchAttrList = attrList.stream().map(baseAttrInfo -> {
            SearchAttr searchAttr = new SearchAttr();
            searchAttr.setAttrId(baseAttrInfo.getId());
            searchAttr.setAttrName(baseAttrInfo.getAttrName());
            //因为skuId 对应的平台属性--属性值只有一个!!!
            searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
            return searchAttr;
        }).collect(Collectors.toList());
        goods.setAttrs(searchAttrList);

        //商品上架
        this.goodsRepository.save(goods);
    }

    //商品下架
    @Override
    public void lowerGoods(Long skuId) {
        this.goodsRepository.deleteById(skuId);
    }

    //更新商品热度
    @Override
    public void incrHotScore(Long skuId) {
        //Zset zincrby increment member
        String hotKey = "hotScore";
        Double count = this.redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);
        if (count%10==0){
            //更新ES
            Optional<Goods> optional = this.goodsRepository.findById(skuId);
            Goods goods = optional.get();
            goods.setHotScore(count.longValue());
            this.goodsRepository.save(goods);
        }
    }

    /**
     * 检索 动态生成dsl 语句!
     * 1.   先生成dsl 语句！  SearchRequest
     * 2.   执行dsl 语句！   SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
     * 3.   将执行之后的结果进行封装 SearchResponseVo
     */
    @Override
    public SearchResponseVo search(SearchParam searchParam) throws IOException {
        //声明一个查询请求对象
        SearchRequest searchRequest = this.bulidDsl(searchParam);
        //执行Dsl语句
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //将执行结果封装SearchResponseVo
        SearchResponseVo searchResponseVo = this.parseResult(searchResponse);
        //默认设置每页显示条数
        searchResponseVo.setPageSize(searchParam.getPageSize());
        searchResponseVo.setPageNo(searchParam.getPageNo());
        //总记录数：totalRecord
        //每页最大记录数：maxResult
        //总页数：totalPage
        //totalPage = (totalRecord + maxResult -1) / maxResult;
        //其中maxResult-1就是totalRecord/maxResult的最大的余数
        Long totalPages = (searchResponseVo.getTotal()+searchParam.getPageSize()-1)/searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);
        //返回数据
        return searchResponseVo;
    }

    //设置返回对象
    private SearchResponseVo parseResult(SearchResponse searchResponse) {
       //声明对象
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        SearchHits hits = searchResponse.getHits();
        //设置总记录数
        searchResponseVo.setTotal(hits.getTotalHits().value);
        //设置商品集合
        //声明一个集合存储商品对象
        List<Goods> goodsList = new ArrayList<>();
        SearchHit[] subHits = hits.getHits();
        if (subHits!=null&&subHits.length>0){
            for (SearchHit subHit : subHits) {
                //获取到source 字符串
                String sourceAsString = subHit.getSourceAsString();
                // 将其转换为Goods
                Goods goods = JSON.parseObject(sourceAsString, Goods.class);
                //判断用户是否根据关键词进行检索 如果是 设置高亮
                if (subHit.getHighlightFields().get("title")!=null){
                    //说明有高亮
                    Text title = subHit.getHighlightFields().get("title").getFragments()[0];
                    goods.setTitle(title.toString());
                }
                //添加商品到集合
                goodsList.add(goods);
            }
        }
        searchResponseVo.setGoodsList(goodsList);

        //获取品牌数据集合
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        //循环遍历 获取品牌id 赋值给SearchResponseVo对象，然后将这个对象添加到集合
        List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map(bucket -> {
            SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
            //获取品牌id
            String tmId = ((Terms.Bucket) bucket).getKeyAsString();
            searchResponseTmVo.setTmId(Long.parseLong(tmId));

            //获取品牌Name
            ParsedStringTerms tmNameAgg = ((Terms.Bucket) bucket).getAggregations().get("tmNameAgg");
            String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmName(tmName);

            //获取品牌LogoUrl
            ParsedStringTerms tmLogoUrlAgg = ((Terms.Bucket) bucket).getAggregations().get("tmLogoUrlAgg");
            String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchResponseTmVo.setTmLogoUrl(tmLogoUrl);
            return searchResponseTmVo;
        }).collect(Collectors.toList());
        searchResponseVo.setTrademarkList(trademarkList);

        //平台属性值获取 数据类型nested
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        //循环遍历获取平台属性id 平台属性名 平台属性值集合，并返回集合
        List<SearchResponseAttrVo> attrVoList = attrIdAgg.getBuckets().stream().map(bucket -> {
            //声明一个平台属性对象
            SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
            //获取平台属性id
            String attrId = ((Terms.Bucket) bucket).getKeyAsString();
            searchResponseAttrVo.setAttrId(Long.parseLong(attrId));

            //获取平台属性名
            ParsedStringTerms attrNameAgg = ((Terms.Bucket) bucket).getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            searchResponseAttrVo.setAttrName(attrName);

            //获取平台属性值集合
            ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
//            List<? extends Terms.Bucket> buckets = attrValueAgg.getBuckets();
//            ArrayList<String> list = new ArrayList<>();
//            buckets.forEach(value->{
//                String valueName = value.getKeyAsString();
//                list.add(valueName);
//            });
//            searchResponseAttrVo.setAttrValueList(list);
            List<String> valueList = attrValueAgg.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
            searchResponseAttrVo.setAttrValueList(valueList);
            //返回平台属性对象
            return searchResponseAttrVo;
        }).collect(Collectors.toList());
        searchResponseVo.setAttrsList(attrVoList);
        //返回对象
        return searchResponseVo;
    }

    private SearchRequest bulidDsl(SearchParam searchParam) {
        //构建查询器 {}
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //{bool}
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //判断用户是否是根据三级分类Id进行检索
        if (!StringUtils.isEmpty(searchParam.getCategory3Id())) {
            //{filter term}
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        }
        //判断用户是否是根据二级分类Id进行检索
        if (!StringUtils.isEmpty(searchParam.getCategory2Id())) {
            //{filter term}
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
        }
        //判断用户是否是根据一级分类Id进行检索
        if (!StringUtils.isEmpty(searchParam.getCategory1Id())) {
            //{filter term}
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
        }

        //根据平台属性值Id进行过滤
        //  先获取到用户点击的数据：props=24:256G:机身内存&props=106:安卓手机:手机一级
        String[] props = searchParam.getProps();
        if (props!=null&&props.length>0){
            for (String prop : props) {
                //  第一次遍历 prop=24:256G:机身内存
                //  第二次遍历 prop=106:安卓手机:手机一级
                String[] split = prop.split(":");
                //声明中间层的bool
                BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
                //声明内层的bool
                BoolQueryBuilder innerBoolBuilder = QueryBuilders.boolQuery();
                //设置内层
                innerBoolBuilder.must(QueryBuilders.matchQuery("attrs.attrId",split[0]));
                innerBoolBuilder.must(QueryBuilders.matchQuery("attrs.attrValue",split[1]));
                //设置中间层
                boolBuilder.must(QueryBuilders.nestedQuery("attrs",innerBoolBuilder, ScoreMode.None));
                boolQueryBuilder.filter(boolBuilder);
                //boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs",innerBoolBuilder,ScoreMode.None));
            }
        }
        //根据关键词检索
        if (!StringUtils.isEmpty(searchParam.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));
            //高亮  必须有检索才能有高亮
            //HighlightBuilder highlightBuilder = searchSourceBuilder.highlighter().field("title").preTags("<span style=color:red>").postTags("</span>");
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title").preTags("<span style=color:red>").postTags("</span>");
            searchSourceBuilder.highlighter(highlightBuilder);
//            HighlightBuilder highlightBuilder = new HighlightBuilder();
//            highlightBuilder.field("title");
//            highlightBuilder.postTags("</span>");
//            highlightBuilder.preTags("<span style=color:red>");
//            searchSourceBuilder.highlighter(highlightBuilder);

        }
        //根据品牌Id进行检索   判断品牌Id
        String trademark = searchParam.getTrademark();
        //trademark获取的数据是 3:华为 需要进行分割
        if (!StringUtils.isEmpty(searchParam.getTrademark())){
            //分割 根据:进行分割
            String[] split = trademark.split(":");
            if (split!=null && split.length == 2){
                //{filter term}
                boolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }

        // 执行查询方法
        searchSourceBuilder.query(boolQueryBuilder);
        //分页
        //from 表示当前的起始条数
        int from = (searchParam.getPageNo()-1)*searchParam.getPageSize();
        searchSourceBuilder.from(from);
        //默认显示3条
        searchSourceBuilder.size(searchParam.getPageSize());

        //排序 规则不唯一 根据页面传递的数据
        String order = searchParam.getOrder();
        if (!StringUtils.isEmpty(order)){
            //分割order
            String[] split = order.split(":");
            if (split!=null && split.length==2){
                String field = "";
                //  需要知道排序的字段与规则. 排序的字段是由第一个参数决定，规则是由第二个参数决定！
                //1 = hotScore热度   2 = price价格
                switch (split[0]){
                    case "1":
                        field = "hotScore";
                        //searchSourceBuilder.sort("hotScore","asc".equals(split[1]? SortOrder.ASC:SortOrder.DESC));
                        break;
                    case "2":
                        field = "price";
                        break;
                }
                searchSourceBuilder.sort(field,"asc".equals(split[1])? SortOrder.ASC:SortOrder.DESC);
            }
        }else{
            searchSourceBuilder.sort("hotScore",SortOrder.DESC);
        }
        //聚合:去重 显示
        //第一部分 品牌
        searchSourceBuilder.aggregation(AggregationBuilders.terms("tmIdAgg").field("tmId")
                .subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
                .subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl")));
        //第二部分 平台属性 数据类型 nested
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg","attrs")
        .subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
                .subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
                .subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));
        //声明一个请求对象 GET/goods/_search 在哪个索引库查询
        SearchRequest searchRequest = new SearchRequest("goods");
        //看到dsl语句
        System.out.println("dsl:\t = " + searchSourceBuilder.toString());
        //设置那些field
        searchSourceBuilder.fetchSource(new String[]{"id","defaultImg","title","price","createTime"},null);
        //{query bool}
        //searchSourceBuilder.query(boolQueryBuilder);
        //将DSL语句赋值给查询对象
        searchRequest.source(searchSourceBuilder);
        return searchRequest;
    }

}
